package com.ruoyi.api.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/*
1、创建ServerSocketChannel通道对象，是所有客户端通道的父通道，专门用于负责处理客户端的连接请求

2、绑定服务器监听端口，设置是否阻塞模式

3、创建多路复用选择器Selector对象，可以创建一个或者多个

4、将ServerSocketChannel以及监听的客户端连接事件（ACCEPT事件）一起注册到Selector上（只需要监听ACCEPT事件即可，专门用于处理客户端的连接请求，至于和客户端读写数据的交互再另外创建通道实现）

5、死循环不停的轮训Selector上注册的所有的通道是否触发了注册的事件

5.1、通过调用Selector的select()方法，该方法会阻塞当前线程，直到至少有一个注册的通道触发了对应的事件才会取消阻塞，然后通过SelectedKeys方法获取所有触发了事件的通道

5.2、遍历所有的SelectionKey，根据触发的事件的类型，进行不同的处理

6、当监听到客户端连接事件之后，为客户端创建SocketChannel用于TCP数据通信，并且将该通道和可读事件(ON_READ)注册到Selector上

7、当监听到客户端可读事件之后，表示客户端向服务器发送数据，那么为该通道创建一定大小的缓冲区，将通道中的数据写入到缓冲区

8、业务处理逻辑从缓冲区读取客户端发送来的数据，进行解析和业务处理

9、服务器通过调用channel的write方法回写数据存入buffer中，（不需要关闭channel，channel是客户端断开了连接之后，服务端会接收到ON_READ事件，然后报错就知道channel断开了）
* */
public class NioServer {

    /** 服务端通道 */
    private static ServerSocketChannel serverSocketChannel;

    /** 多路复用选择器*/
    private static Selector selector;

    public static void main(String[] args) {
        try {
            //1.初始化服务器
            initServer();
            //2.启动服务器
            startServer();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /** 初始化服务器*/
    private static void initServer() throws IOException {
        /** 1.创建服务端通道 */
        serverSocketChannel = ServerSocketChannel.open();

        //设置通道为非阻塞类型
        serverSocketChannel.configureBlocking(false);

        /** 2. 绑定监听端口号 */
        serverSocketChannel.socket().bind(new InetSocketAddress(8000));

        /** 3. 创建多路复用选择器 */
        selector = Selector.open();

        /** 4. 注册通道监听的事件 */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**  启动服务器 */
    private static void startServer() throws IOException {
        System.out.println("Start Server...");
        while (true){
            /** 1.不停轮训获取所有的channel的状态 */
            selector.select(); //阻塞当前线程，直到至少有一个通道触发了对应的事件
            /** 2.获取所有触发了注册事件的channel及事件集合 */
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            /** 3.遍历处理所有channel的事件 */
            while(iterator.hasNext()){
                SelectionKey key = iterator.next();
                /** 4.根据不同的事件类型处理不同的业务逻辑 */
                if(key.isAcceptable()){
                    //表示当前通道接收连接成功,主要用于服务端接收到客户端连接成功
                    SocketChannel channel = serverSocketChannel.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                }else if(key.isConnectable()){
                    //表示当前通道连接成功,主要用于客户端请求服务器连接成功
                }else if(key.isReadable()){
                    //表示当前通道有可读的数据
                    receiveMsg(key);
                }else if(key.isWritable()){
                    //表示当前通道可以写入数据,（网络不阻塞的情况下,基本上一直处于可写状态,除非缓冲区满了）

                }
                iterator.remove();
            }
        }
    }

    /** 读取数据,当事件为可读事件时调用 */
    private static void receiveMsg(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        /** 1.分配2048大小的缓存区大小 */
        ByteBuffer buffer = ByteBuffer.allocate(2048);
        /** 2.将channel中数据读到缓冲区,并返回数据大小 */
        int i = channel.read(buffer);
        if(i != -1){
            /** 3.从缓冲区获取所有数据,解析成字符串*/
            String msg = new String(buffer.array()).trim();
            System.out.println("服务器接收到消息:" + msg);
            /** 4.调用write方法向channel中发送数据 */
            channel.write(ByteBuffer.wrap(("reply : " + msg).getBytes()));
        }else {
            channel.close();
        }
    }
}
